﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Wintellect.PowerCollections;
using System.Diagnostics;

using DbSharp.Framework.DbObject;
using DbSharp.Framework.CaseFile;
using DbSharp.Framework.Assertion;
using DbSharp.Framework.Storage;
using DbSharp.Framework.Storage.IO;
using DbSharp.Framework.CaseFile.Parser;
using DbSharp.Framework.Common;
using DbSharp.Framework.Assertion.Xml;
using DbSharp.Framework.Driver;


namespace DbSharp.Framework.TestEngine
{
    public abstract class TestRunnerBase
    {
        #region Protected members

        protected CaseTestResult m_CaseExecuteResult;

        #endregion

        #region Protected methods

        protected void ExportResultsetToXmlFile(StatementRunner runner)
        {
            StmtExecutionResult executionResult = runner.ExecutionResult;

            foreach (var item in executionResult.ListScalarExecutionResult)
            {
                if (item.Storage != null)
                {
                    IScalarStorage storage = item.Storage;
                    string xmlFile;
                    StorageNameMaker.PrepareExportXmlFileName(out xmlFile);
                    item.ExportToXmlFile(xmlFile, storage);
                    item.ExportedFileName = xmlFile;
                }
            }

            foreach (var item in executionResult.ListRecordsetExecutionResult)
            {
                if (item.Storage != null)
                {
                    IRecordsetStorage storage = item.Storage;
                    long exportRecordCount = runner.GetExportRecordCount();
                    string xmlFile;
                    StorageNameMaker.PrepareExportXmlFileName(out xmlFile);
                    item.ExportToXmlFile(xmlFile, storage, exportRecordCount);
                    item.ExportedFileName = xmlFile;
                }
            }
        }

        protected void DetectUserInterrupt()
        {
            if (RuntimeInfo.GetInstance().Interrupted)
                throw new IgnoreExecutionException();
        }

        /// <summary>
        /// execute setup actions
        /// </summary>
        /// <param name="listSetupAction"></param>
        /// <param name="transController"></param>
        /// <param name="storageFactory"></param>
        protected void PerformSetupProcess(IEnumerable<SetupAction> listSetupAction,
            TransactionController transController, IStorageFactory storageFactory,
            ITestCaseStatusNotify statusNotifier, bool isSetup)
        {
            #region Update status
            if (listSetupAction.Count() > 0)
            {
                TestCaseStatusNotifyEventArgs statusArgs = null;
                if (statusNotifier != null)
                {
                    statusArgs = new TestCaseStatusNotifyEventArgs();
                    if (isSetup)
                        statusArgs.TestPhase = CaseRunPhase.Setup;
                    else
                        statusArgs.TestPhase = CaseRunPhase.Teardown;
                    statusArgs.ErrorMessage = string.Empty;
                    statusArgs.TestRunner = this;
                    statusNotifier.TestRunner = this;
                    statusNotifier.UpdateStatus(statusArgs);
                }
            }
            #endregion

            foreach (var action in listSetupAction)
            {
                if (action.ActionType == SetupActionType.OrdinaryStatement)
                {
                    var statement = (IStatementDeclaration)action.Action;
                    var runner = new StatementRunner(transController, statement, null, storageFactory, null);
                    runner.Run(null);
                }
                else if (action.ActionType == SetupActionType.DataPumpAction)
                {
                    var pumpSection = (DataPumpSection)action.Action;
                    var runner = new DataPumpActionRunner(pumpSection);
                    runner.Run(CaseName, transController, storageFactory);
                }
            }
        }

        #endregion

        #region Public property/method

        public string CaseName { get; set; }

        public string Tags { get; set; }

        public CaseTestResult CaseExecuteResult
        {
            get { return m_CaseExecuteResult; }
        }

        public abstract void Run(CaseRunType runType, ITestCaseStatusNotify statusNotifier);

        #endregion
    }


    public class PerfTestRunner : TestRunnerBase
    {
        #region Private members

        private PerformanceTestCase m_TestCase;

        #endregion

        public PerfTestRunner(PerformanceTestCase testCase)
        {
            m_TestCase = testCase;
            CaseName = m_TestCase.TestCaseName;
            Tags = m_TestCase.TagsValue;
            m_CaseExecuteResult = new CaseTestResult(CaseName, null);
        }


        #region Public property/method

        public PerformanceTestCase TestCase
        {
            get { return m_TestCase; }
        }


        /// <summary>
        /// For runType=TestRunType.Test, we need to recompute runType in order to support Data Pump action in setup or teardown
        /// </summary>
        /// <param name="runType"></param>
        /// <returns></returns>
        private CaseRunType ChooseSuitableRunType(CaseRunType runType)
        {
            CaseRunType result = runType;

            if (runType == CaseRunType.Test)
            {
                bool needStorage = false;
                foreach (var action in m_TestCase.ListSetupAction)
                {
                    if (action.ActionType == SetupActionType.DataPumpAction)
                    {
                        needStorage = true;
                        break;
                    }
                }
                foreach (var action in m_TestCase.ListTeardownAction)
                {
                    if (action.ActionType == SetupActionType.DataPumpAction)
                    {
                        needStorage = true;
                        break;
                    }
                }
                if (needStorage)
                    result = CaseRunType.ExportXml;
            }
            return result;
        }

        public override void Run(CaseRunType runType, ITestCaseStatusNotify statusNotifier)
        {
            runType = ChooseSuitableRunType(runType);
            var testRunEnv = PerfCaseRunEnv.GetRuntimeEnvironment(runType);
            IStorageFactory storageFactory = testRunEnv.StorageFactory;

            #region Update status
            TestCaseStatusNotifyEventArgs statusArgs = null;
            if (statusNotifier != null)
            {
                statusArgs = new TestCaseStatusNotifyEventArgs();
                statusArgs.TestPhase = CaseRunPhase.Initial;
                statusArgs.ErrorMessage = string.Empty;
                statusArgs.TestRunner = this;

                statusNotifier.TestRunner = this;
                statusNotifier.UpdateStatus(statusArgs);
            }
            #endregion

            try
            {
                StatementRunner statementRunner;

                DetectUserInterrupt();

                #region step 2 : run db statement
                bool hasSetupOrTeardown = false;
                if (m_TestCase.ListSetupAction.Count > 0)
                    hasSetupOrTeardown = true;
                if (m_TestCase.ListTeardownAction.Count > 0)
                    hasSetupOrTeardown = true;

                using (TransactionController transController = new TransactionController(m_TestCase.TestInSandbox, hasSetupOrTeardown))
                {
                    //execute setup actions
                    PerformSetupProcess(m_TestCase.ListSetupAction, transController, storageFactory, statusNotifier, true);

                    statementRunner = new StatementRunner(transController, m_TestCase.StatementDeclaration,
                        m_TestCase.ResultsetSection, storageFactory, null);

                    m_CaseExecuteResult.Reset();
                    statementRunner.Run(statusNotifier);

                    //execute teardown actions
                    PerformSetupProcess(m_TestCase.ListTeardownAction, transController, storageFactory, statusNotifier, false);
                }
                #endregion

                #region step 2: export xml file
                DetectUserInterrupt();
                if (testRunEnv.WillExport)
                {
                    #region Update status
                    if (statusNotifier != null)
                    {
                        statusArgs.TestPhase = CaseRunPhase.Export;
                        statusArgs.ErrorMessage = string.Empty;
                        statusNotifier.UpdateStatus(statusArgs);
                    }
                    #endregion

                    ExportResultsetToXmlFile(statementRunner);
                    CaseExecuteResult.ExportedFlag = true;
                }
                #endregion

                Trace.TraceInformation("PT-step3:" + DateTime.Now.Ticks);

                //add sql executionResult into caseExecutionResult
                m_CaseExecuteResult.TestState = CaseTestState.Successful;
                statementRunner.ExecutionResult.IsTargetResultset = true;
                m_CaseExecuteResult.ListStatementResult.Add(statementRunner.ExecutionResult);
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                m_CaseExecuteResult.TestState = CaseTestState.Failed;
                m_CaseExecuteResult.ErrorHint = ex.Message;
                if (statusNotifier != null)
                    statusArgs.ErrorMessage = "Error: " + ex.Message;
            }
            finally
            {
                if (statusNotifier != null)
                {
                    statusArgs.TestPhase = CaseRunPhase.Finished;
                    statusNotifier.UpdateStatus(statusArgs);
                }
            }
        }

        #endregion

    }


    public class UnitTestRunner : TestRunnerBase
    {
        #region Private members

        private bool m_AlreadyRunBeforeExport;
        private bool m_ForceRunBeforeExport;
        private UnitTestCase m_TestCase;
        private StatementRunner m_SqlRunner1;
        private StatementRunner m_SqlRunner2;
        private IStatementDeclaration m_Sql1;
        private IStatementDeclaration m_Sql2;
        private IAssertionFactory m_AssertionFactory = new XmlAssertionFactory();


        #endregion

        #region Private methods

        private bool ValidateResultset(StatementRunner runner1, StatementRunner runner2)
        {

            bool result = true;
            bool foundActual, foundExpected;
            object actual;
            object expected;
            bool isRecordset;
            int indexInList;
            List<string> actualFieldList = null;
            List<string> expectedFieldList = null;

            List<FieldMetaData> actualFieldMetaDataQueue;
            List<FieldMetaData> expectedFieldMetaDataQueue;

            ITestDataSource actualDataSource = null;
            ITestDataSource expectedDataSource = null;

            string exceptionFmt = "outputArgument of {0} section in Test Case {1} does not exist.";

            foundActual = runner1.TryGetAssertionResultset(out actual, out actualFieldList, out actualFieldMetaDataQueue, out isRecordset, out indexInList);
            if (foundActual == false)
                throw new DbSharpException(string.Format(exceptionFmt, "targetResultset", CaseName));

            foundExpected = runner2.TryGetAssertionResultset(out expected, out expectedFieldList, out expectedFieldMetaDataQueue, out isRecordset, out indexInList);
            if (foundExpected == false)
                throw new DbSharpException(string.Format(exceptionFmt, "referenceResultset", CaseName));
            
            AssertionType assertType = m_TestCase.Assertion;

            actualDataSource = runner1.TestDataSource;
            expectedDataSource = runner2.TestDataSource;

            var assertion = m_AssertionFactory.CreateAssertion(assertType);
            result = assertion.Claim(actual, expected, actualFieldMetaDataQueue, expectedFieldMetaDataQueue, actualDataSource, expectedDataSource);

            return result;
        }


        private void InternalRun(bool willValidateData, ITestCaseStatusNotify statusNotifier)
        {
            m_CaseExecuteResult.Reset();
            DetectUserInterrupt();
            m_AlreadyRunBeforeExport = false;

            var testRunEnv = UnitCaseRunEnv.GetRuntimeEnvironment();
            IStorageFactory storageFactory = testRunEnv.StorageFactory;

            #region step1 : run db statement
            bool hasSetupOrTeardown = false;
            if (m_TestCase.ListSetupAction.Count > 0)
                hasSetupOrTeardown = true;
            if (m_TestCase.ListTeardownAction.Count > 0)
                hasSetupOrTeardown = true;
            using (TransactionController transController = new TransactionController(m_TestCase.TestInSandbox, hasSetupOrTeardown))
            {
                //execute setup actions
                PerformSetupProcess(m_TestCase.ListSetupAction, transController, storageFactory, statusNotifier, true);

                //execute target and refer statement 
                m_SqlRunner1 = new StatementRunner(transController, m_Sql1, m_TestCase.TargetResultsetSection, storageFactory, null);
                m_SqlRunner2 = new StatementRunner(transController, m_Sql2, m_TestCase.ReferResultsetSection, storageFactory, null);

                Trace.TraceInformation("UT-step3A1:" + DateTime.Now.Ticks);
                m_SqlRunner1.Run(statusNotifier);
                m_SqlRunner2.Run(statusNotifier);

                //execute teardown actions
                PerformSetupProcess(m_TestCase.ListTeardownAction, transController, storageFactory, statusNotifier, false);

                m_AlreadyRunBeforeExport = true;
                Trace.TraceInformation("UT-step3A2:" + DateTime.Now.Ticks);
            }
            #endregion

            DetectUserInterrupt();
            #region step 2: validate data
            if (willValidateData)
            {
                #region Update status
                if (statusNotifier != null)
                {
                    var statusArgs = new TestCaseStatusNotifyEventArgs();
                    statusArgs.TestRunner = statusNotifier.TestRunner;
                    statusArgs.TestPhase = CaseRunPhase.Assertion;
                    statusNotifier.UpdateStatus(statusArgs);
                }
                #endregion

                Trace.TraceInformation("UT-step3A3:" + DateTime.Now.Ticks);
                bool valid = ValidateResultset(m_SqlRunner1, m_SqlRunner2);
                Trace.TraceInformation("UT-step3A4:" + DateTime.Now.Ticks);

                if (valid)
                    m_CaseExecuteResult.TestState = CaseTestState.Successful;
                else
                {
                    m_CaseExecuteResult.TestState = CaseTestState.Failed;
                    m_CaseExecuteResult.ErrorHint = "Not meet the assertion";
                }
            }
            #endregion

            //add sql executionResult into caseExecutionResult
            m_SqlRunner1.ExecutionResult.IsTargetResultset = true;
            m_CaseExecuteResult.ListStatementResult.Add(m_SqlRunner1.ExecutionResult);
            m_SqlRunner2.ExecutionResult.IsTargetResultset = false;
            m_CaseExecuteResult.ListStatementResult.Add(m_SqlRunner2.ExecutionResult);
        }


        /// <summary>
        /// to get the exported file of the resultset for assertion
        /// </summary>
        /// <param name="sqlRunner"></param>
        /// <returns></returns>
        private string GetAssertResultsetExportFile(StatementRunner sqlRunner)
        {
            string exportedFile = string.Empty;
            object resultsetCarrier;
            List<string> resultsetFieldList = null;
            List<FieldMetaData> resultsetFieldMetaData;
            bool isRecordset;
            int indexInList;
            bool found = false;
            found = sqlRunner.TryGetAssertionResultset(out resultsetCarrier, out resultsetFieldList, out resultsetFieldMetaData, out isRecordset, out indexInList);
            if (found)
            {
                if (isRecordset)
                    exportedFile = sqlRunner.ExecutionResult.ListRecordsetExecutionResult[indexInList].ExportedFileName;
                else
                    exportedFile = sqlRunner.ExecutionResult.ListScalarExecutionResult[indexInList].ExportedFileName;
            }
            return exportedFile;
        }


        #endregion

        public UnitTestRunner(UnitTestCase testCase)
        {
            m_TestCase = testCase;
            CaseName = m_TestCase.TestCaseName;
            Tags = m_TestCase.TagsValue;

            m_Sql1 = m_TestCase.TargetResultsetSection.StatementDeclaration;
            m_Sql2 = m_TestCase.ReferResultsetSection.StatementDeclaration;
            m_CaseExecuteResult = new CaseTestResult(CaseName, m_TestCase.Assertion);
        }

        #region Public property/method

        public UnitTestCase TestCase
        {
            get { return m_TestCase; }
        }


        public bool ForceRunBeforeExport
        {
            get { return m_ForceRunBeforeExport; }
            set
            {
                m_ForceRunBeforeExport = value;
                if (m_ForceRunBeforeExport)
                    m_AlreadyRunBeforeExport = false;
            }
        }


        public StatementRunner TargetStmtRunner
        {
            get { return m_SqlRunner1; }
        }


        public StatementRunner ReferStmtRunner
        {
            get { return m_SqlRunner2; }
        }


        public override void Run(CaseRunType runType, ITestCaseStatusNotify statusNotifier)
        {
            Trace.TraceInformation("UT-step1:" + DateTime.Now.Ticks);
            #region Update status
            TestCaseStatusNotifyEventArgs statusArgs = null;
            if (statusNotifier != null)
            {
                statusArgs = new TestCaseStatusNotifyEventArgs();
                statusArgs.TestPhase = CaseRunPhase.Initial;
                statusArgs.ErrorMessage = string.Empty;
                statusArgs.TestRunner = this;

                statusNotifier.TestRunner = this;
                statusNotifier.UpdateStatus(statusArgs);

            }
            #endregion
            Trace.TraceInformation("UT-step2:" + DateTime.Now.Ticks);

            try
            {
                if (runType == CaseRunType.Test)
                {
                    Trace.TraceInformation("UT-step3A:" + DateTime.Now.Ticks);
                    DetectUserInterrupt();
                    InternalRun(true, statusNotifier);
                    Trace.TraceInformation("UT-step4A:" + DateTime.Now.Ticks);
                }
                else // ExportXml
                {
                    Trace.TraceInformation("UT-step3B:" + DateTime.Now.Ticks);
                    DetectUserInterrupt();
                    if (m_AlreadyRunBeforeExport == false)
                    {
                        InternalRun(false, statusNotifier);
                    }

                    Trace.TraceInformation("UT-step4B:" + DateTime.Now.Ticks);

                    #region Update status
                    if (statusNotifier != null)
                    {
                        statusArgs.TestPhase = CaseRunPhase.Export;
                        statusNotifier.UpdateStatus(statusArgs);
                    }
                    #endregion

                    #region Export xml files
                    DetectUserInterrupt();
                    ExportResultsetToXmlFile(m_SqlRunner1);
                    DetectUserInterrupt();
                    ExportResultsetToXmlFile(m_SqlRunner2);
                    #endregion

                    m_CaseExecuteResult.TestState = CaseTestState.Uncertain;
                    m_CaseExecuteResult.ExportedFlag = true;

                    m_CaseExecuteResult.TargetRsltsetExportFile = GetAssertResultsetExportFile(m_SqlRunner1);
                    m_CaseExecuteResult.ReferRsltsetExportFile = GetAssertResultsetExportFile(m_SqlRunner2);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                m_CaseExecuteResult.TestState = CaseTestState.Failed;
                string errorFmt = "Message: {0}; Source: {1}";
                string exceptionSource = ex.Source;
                if (string.IsNullOrEmpty(exceptionSource))
                    exceptionSource = "Unknown";
                m_CaseExecuteResult.ErrorHint = string.Format(errorFmt, ex.Message, exceptionSource);
                 if (statusNotifier != null)
                     statusArgs.ErrorMessage = string.Format(" Error: [{0}]", m_CaseExecuteResult.ErrorHint);
            }
            finally
            {
                if (statusNotifier != null)
                {
                    statusArgs.TestPhase = CaseRunPhase.Finished;
                    statusNotifier.UpdateStatus(statusArgs);
                }
            }

        }


        #endregion
    }


}
